package Twitter.RequestSender;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

import twitter.Server.ServerType;
import twitter.TwitterLibrary.SocketConfig;
import twitter.TwitterLibrary.Utility;

/*******************************
 * this class is designed for sending request between one machine to another
 * machine
 * 
 * @author fang
 * 
 */
public class RequestSender {
	private Socket clientSocket = null;
	static Logger logger = Logger.getLogger(RequestSender.class);
	private final ReentrantReadWriteLock rwl ;
	private final Lock w;
	private final Lock r;
	protected Utility utility;

	/*************************************
	 * Class constructor
	 */
	public RequestSender(Utility utility) {
		
		BasicConfigurator.configure();
		this.utility=utility;
		rwl=utility.getLock();
		this.w=rwl.writeLock();
		this.r=rwl.readLock();
		
	}

	/***********************************************
	 * send request from this machine to another machine
	 * 
	 * @param hostname
	 *            :another machine
	 * @param queryString
	 *            :the query string
	 * @param port
	 *            :the port
	 * @return
	 */
	public String sendRequest(String hostname, String queryString, int port) {

		PrintWriter out = null;
		BufferedReader in = null;
		InputStream ins = null;
		InputStreamReader isr = null;
		String result = "";
		BufferedReader br = null;
		logger.info("send one request to " + hostname + " at port : " + port
				+ " " + queryString);
		System.out.println("send one request to " + hostname + " at port : " + port
				+ " " + queryString);
		try {
			clientSocket = new Socket(hostname, port);
			out = new PrintWriter(clientSocket.getOutputStream(), true);
			in = new BufferedReader(new InputStreamReader(clientSocket
					.getInputStream()));
			out.print(queryString);
			out.flush();
			System.out.println("queryString " + queryString);

			ins = clientSocket.getInputStream();
			isr = new InputStreamReader(ins);
			br = new BufferedReader(isr);
			StringBuilder sb = new StringBuilder();
			String fromBackend = "";

			while ((fromBackend = in.readLine()) != null) {
				sb.append(fromBackend);

				clientSocket.getOutputStream().close();
				out.close();

			}
			result = sb.toString();

			System.out.println("result " + result);

		} catch (java.net.ConnectException e) {
			System.out.println("exception !!!!!!!!!!!!!!!!"+e.getMessage());
			connectionClosed(hostname);
			
		} catch(java.net.SocketException e)
		{
			System.out.println("exception !!!!!!!!!!!!!!!!!!!!!!"+e.getMessage());
			connectionClosed(hostname);
		}
		
		catch (UnknownHostException e) {
			
			logger.error("Don't know about host: localhost.");
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (in != null) {
					in.close();
				}
				if (clientSocket != null) {
					clientSocket.close();
				}
				if (isr != null) {
					isr.close();
				}
				if (br != null) {
					br.close();
				}
				if (ins != null) {
					ins.close();
				}

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

		return result;

	}

	private String contructDeleteNotice(String serverName) {
		String notice = serverName + "\r\n remove \r\n";

		return notice;
	}

	public void sendRequestToEveryServer(String excluedServer, String query) {
		r.lock();
		
		
		try{
		Set<String> keys = SocketConfig.serverToType.keySet();
		Iterator<String> iter = keys.iterator();
	
		while (iter.hasNext()) {
	
			String ip = iter.next();
			System.out.println("primary server's server information: " + ip
					+ " " + SocketConfig.serverToType.get(ip));
			
			if (!ip.equals(excluedServer) && (!ip.equals(SocketConfig.myIp))) {
				ServerType type = SocketConfig.serverToType.get(ip);
				if (type == ServerType.BackendServer) {
					this.sendRequest(ip, query, SocketConfig.backendPort);
				} else if (type == ServerType.FrontServer) {
					this.sendRequest(ip, query, SocketConfig.frontendport);
				}
			}
		}
		}
		finally
		{
			r.unlock();
		}
	}

	public void sendRequestToEveryBackendServer(String query) {
		Set<String> keys = SocketConfig.serverToType.keySet();
		Iterator<String> iter = keys.iterator();
		
		while (iter.hasNext()) {
		
			String ip = iter.next();
			System.out.println("primary server's server information: " + ip
					+ " " + SocketConfig.serverToType.get(ip));
			if (SocketConfig.serverToType.get(ip) == ServerType.BackendServer) {
				this.sendRequest(ip, query, SocketConfig.backendPort);
			}

		}
	}
	public void startElection(Collection Servers,String oldPrimaryServer)
	{
		Servers.remove(oldPrimaryServer);
		//String notice = contructDeleteNotice(server);
		//sendRequestToEveryServer(hostname, notice);
		utility.electionResponseResult.clear();
		Set ServerIdentifiers = SocketConfig.identifierToServer.keySet();
		Iterator itertt = ServerIdentifiers.iterator();
		String election=SocketConfig.myIp+" "+SocketConfig.identifier+"\r\n election \r\n";
		System.out.println(election);
		System.out.println("out of election loop");
		while (itertt.hasNext()) {
			
			int tempIdentifier = Integer.parseInt(itertt.next().toString());
			System.out.println("in the election loop my identifier =="+SocketConfig.identifier+"another server's identifier =="+tempIdentifier);
			String serverip=SocketConfig.identifierToServer.get(String.valueOf(tempIdentifier));
			System.out.println("SocketConfig.serverToType.get(serverip) "+SocketConfig.serverToType.get(serverip));
			if((SocketConfig.identifier<tempIdentifier)&&(SocketConfig.serverToType.get(serverip)==ServerType.BackendServer))
			{
				System.out.println("==============send one election message");
				String response=this.sendRequest(serverip, election, SocketConfig.backendPort);
				if((response!=null)&&(!response.equals("")))
				{
					utility.electionResponseResult.add(serverip);
				}
			}
		}
		if(utility.electionResponseResult.size()==0)
		{// elect itself
			System.out.println("==================================I am the new primary server");
			SocketConfig.serverType=ServerType.PrimaryServer;
			SocketConfig.primaryIp=SocketConfig.myIp;
			if(SocketConfig.serverToType.get(SocketConfig.primaryIp)==ServerType.BackendServer)
			{
				SocketConfig.serverToType.put(SocketConfig.primaryIp, ServerType.PrimaryServer);
				System.out.println("|||||||||||||||||change my tpye from back end to primary server");
			}
			
			ServerRegistrationSender registration=new ServerRegistrationSender(this.utility);
			utility.threadPool.execute(registration);
		}
		
		
	}


	
	private void connectionClosed(String hostname)
	{
		
	
		Collection Servers = SocketConfig.identifierToServer.values();
		Iterator iter = Servers.iterator();
		while (iter.hasNext()) {

			String server = iter.next().toString();
			System.out.println("************" + server+"  "+hostname+" "+SocketConfig.serverToType.get(hostname));
			if ((server.equals(hostname))
					&& ((SocketConfig.serverToType.get(hostname)) != ServerType.PrimaryServer)&&
					(SocketConfig.serverType==ServerType.PrimaryServer)) {
				System.out.println("the server " + hostname + "has been shutdown");
				
				Servers.remove(server);
				
				String notice = contructDeleteNotice(server);
				sendRequestToEveryServer(server, notice);
				break;
			} else if (SocketConfig.serverToType.get(hostname) == ServerType.PrimaryServer) {
				System.out.println("the primary server " + hostname + "has been shutdown");
				startElection(Servers,hostname);
	
	}}}
}
